package org.dru.clay.respository.transport.filesystem;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.net.URI;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;

import org.dru.clay.respository.transport.FileInfo;
import org.dru.clay.respository.transport.Transport;
import org.dru.clay.util.io.IOUtils;

public class FileSystemTransport implements Transport {
	private static final Logger logger = Logger.getLogger(FileSystemTransport.class.getName());

	private final Set<FileSystemOptions> options;

	public FileSystemTransport(FileSystemOptions... options) {
		if (options == null || options.length == 0) {
			this.options = EnumSet.noneOf(FileSystemOptions.class);
		} else {
			this.options = EnumSet.<FileSystemOptions> copyOf(Arrays.asList(options));

		}
	}

	@Override
	public FileInfo get(URI uri) {
		if (!"file".equalsIgnoreCase(uri.getScheme())) {
			throw new IllegalArgumentException("Invalid scheme for " + this.getClass().getName());
		}

		try {
			final File file = new File(uri.getPath());
			return new FileInfo(file.length(), file.lastModified(), IOUtils.read(new FileInputStream(file)));
		} catch (FileNotFoundException e) {
			throw new RuntimeException(e);
		}
	}

	@Override
	public void get(URI file, File destination) {
		if (!"file".equalsIgnoreCase(file.getScheme())) {
			throw new IllegalArgumentException("Invalid scheme for " + this.getClass().getName());
		}
		copy(new File(file.getPath()), destination, true);
	}

	@Override
	public void getIfNewer(URI file, File destination) {
		if (!"file".equalsIgnoreCase(file.getScheme())) {
			throw new IllegalArgumentException("Invalid scheme for " + this.getClass().getName());
		}
		copy(new File(file.getPath()), destination, false);
	}

	@Override
	public void put(File source, URI destination) {
		if (!"file".equalsIgnoreCase(destination.getScheme())) {
			throw new IllegalArgumentException("Invalid scheme for " + this.getClass().getName());
		}
		copy(source, new File(destination.getPath()), true);
	}

	@Override
	public List<URI> list(URI directory) {
		if (!"file".equalsIgnoreCase(directory.getScheme())) {
			throw new IllegalArgumentException("Invalid scheme for " + this.getClass().getName());
		}
		final File dir = new File(directory.getPath());
		final List<URI> list = new ArrayList<URI>();
		if (dir.exists()) {
			for (File file : dir.listFiles()) {
				list.add(file.toURI());
			}
		}
		return list;
	}

	private void copy(File source, File destination, boolean overwrite) {
		createDirectory(destination.getParentFile());

		if (!overwrite && destination.exists() && destination.lastModified() >= source.lastModified()) {
			logger.info("File on disk is same or newer : " + destination);
			return;
		}

		FileInputStream fileInputStream = null;
		FileOutputStream fileOutputStream = null;
		try {
			fileInputStream = new FileInputStream(source);
			fileOutputStream = new FileOutputStream(destination);

			final FileChannel channel = fileOutputStream.getChannel();
			/* long bytes = */fileInputStream.getChannel().transferTo(0, Integer.MAX_VALUE, channel);
		} catch (Exception e) {
			throw new RuntimeException(e);
		} finally {
			IOUtils.silentClose(fileInputStream);
			IOUtils.silentClose(fileOutputStream);
		}
		
		destination.setLastModified(source.lastModified());
	}

	private void createDirectory(File directory) {
		if (!options.contains(FileSystemOptions.CreateDirectories) || !directory.isDirectory()) {
			return;
		}
		directory.mkdirs();
	}

	@Override
	public void cleanup() {
	}
}
